package com.funtl.screw.auth.config;

import com.funtl.screw.common.security.component.ServiceCommenceAuthExceptionEntryPoint;
import com.funtl.screw.common.security.component.ServiceWebResponseExceptionTranslator;
import com.funtl.screw.common.security.service.ServiceCustomTokenServices;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import java.util.Collections;

/**
 * 认证服务器配置
 *
 * @author lusifer
 */
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final ClientDetailsService serviceClientDetailsServiceImpl;

    private final AuthenticationManager authenticationManagerBean;

    private final UserDetailsService serviceUserDetailsService;

    private final AuthorizationCodeServices authorizationCodeServices;

    private final TokenStore redisTokenStore;

    private final TokenEnhancer tokenEnhancer;

    private final ObjectMapper objectMapper;

    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.withClientDetails(serviceClientDetailsServiceImpl);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer.allowFormAuthenticationForClients()
                .authenticationEntryPoint(new ServiceCommenceAuthExceptionEntryPoint(objectMapper))
                .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .tokenServices(tokenServices()).tokenStore(redisTokenStore)
                .tokenEnhancer(tokenEnhancer).userDetailsService(serviceUserDetailsService)
                .authorizationCodeServices(authorizationCodeServices).authenticationManager(authenticationManagerBean)
                .reuseRefreshTokens(false).pathMapping("/oauth/confirm_access", "/token/confirm_access")
                .exceptionTranslator(new ServiceWebResponseExceptionTranslator());
    }

    /**
     * token 核心处理
     *
     * @return tokenServices
     */
    @Bean
    public ServiceCustomTokenServices tokenServices() {
        ServiceCustomTokenServices tokenServices = new ServiceCustomTokenServices();
        tokenServices.setTokenStore(redisTokenStore);
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setClientDetailsService(serviceClientDetailsServiceImpl);
        tokenServices.setTokenEnhancer(tokenEnhancer);
        addUserDetailsService(tokenServices, serviceUserDetailsService);
        return tokenServices;
    }

    private void addUserDetailsService(ServiceCustomTokenServices tokenServices, UserDetailsService serviceUserDetailsService) {
        if (serviceUserDetailsService != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(serviceUserDetailsService));
            tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider)));
        }
    }
}
